/********************************************************************
	created:	2008/05/30
	created:	30:5:2008   13:17
	filename: 	SkinWnd.cpp
	author:		Dean

	purpose:
*********************************************************************/
#include "StdAfx.h"
#include "SkinWnd.h"
#include <WindowsX.h>
#include "Ini.h"
#include "Resource.h"
#include "SkinButton.h"
#include "SkinProgressCtrl.h"
#include "SkinPanel.h"
#include "SkinLCD.h"
#include "CommandSink.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

#define USE_CACHED_BITMAP	1

CSkinWnd::CInitGDIPlus CSkinWnd::s_initGDIPlus;
UINT	CSkinWnd::s_ControlId = 1500;
//////////////////////////////////////////////////////////////////////////
CSkinWnd::CSkinWnd()
		: m_pWnd(NULL)
		, m_pSkinConfigFile(NULL)
		, m_pImgTopleft(m_pImgs[0]), m_pImgTop(m_pImgs[1]), m_pImgTopright(m_pImgs[2])
		, m_pImgLeft(m_pImgs[3]), m_pImgRight(m_pImgs[4])
		, m_pImgBottomleft(m_pImgs[5]), m_pImgBottom(m_pImgs[6]), m_pImgBottomright(m_pImgs[7])
		, m_pCacheFrameImage(m_pImgs[8])
		, m_ptMinTrackSize(240, 180)
		, m_crBackground(0)
{
	m_rectClient.SetRectEmpty();
	ZeroMemory (m_pImgs, sizeof(m_pImgs));
}

CSkinWnd::~CSkinWnd(void)
{
	FreeSkin();
}

BOOL CSkinWnd::OnWndMsg( UINT message, WPARAM wParam, LPARAM lParam, LRESULT* pResult )
{
	HWND hwnd = m_pWnd->GetSafeHwnd();
	switch (message) {
		HANDLE_MSG(hwnd, WM_SIZE, OnSize);
		HANDLE_MSG(hwnd, WM_LBUTTONDOWN, OnLButtonDown);
		HANDLE_MSG(hwnd, WM_GETMINMAXINFO, OnGetMinMaxInfo);
	}

	return FALSE;
}

bool CSkinWnd::UpdateLayered( Gdiplus::Bitmap* pBmp )
{
	if (NULL == pBmp || NULL == m_pWnd)
		return false;

	CDC memDc;
	CBitmap	bmMem;
	CSize sizeBmp (pBmp->GetWidth(), pBmp->GetHeight());
	CWindowDC dc(m_pWnd);

	memDc.CreateCompatibleDC(&dc);
	bmMem.CreateCompatibleBitmap(&dc, sizeBmp.cx, sizeBmp.cy);
	CBitmap* pbmOld = memDc.SelectObject(&bmMem);
	Gdiplus::Graphics graphic (memDc);
	graphic.DrawImage(pBmp, 0, 0, sizeBmp.cx, sizeBmp.cy);

	CRect rtWnd;
	m_pWnd->GetWindowRect(&rtWnd);

	BLENDFUNCTION blendFunc ;
	blendFunc.BlendOp = AC_SRC_OVER;
	blendFunc.SourceConstantAlpha = 255;
	blendFunc.AlphaFormat = AC_SRC_ALPHA;
	blendFunc.BlendFlags = 0;

	CRgn rgnWnd;
	rgnWnd.CreateRectRgn(0, 0, sizeBmp.cx, sizeBmp.cy);
	m_pWnd->SetWindowRgn(rgnWnd, TRUE);
	m_pWnd->UpdateLayeredWindow(&dc, &rtWnd.TopLeft(), &sizeBmp, &memDc, &CPoint (0, 0), 0, &blendFunc, ULW_ALPHA);

	memDc.SelectObject(pbmOld);

	return true;
}

void CSkinWnd::FreeSkin()
{
	m_strSkinPath.Empty();
	delete m_pSkinConfigFile;
	m_pSkinConfigFile = NULL;

	m_ptMinTrackSize.SetPoint(0, 0);
	m_rectClient.SetRectEmpty();
	m_strLogImage.Empty();

	for (int i = 0; i < sizeof(m_pImgs) / sizeof(m_pImgs[0]); i++) {
		delete m_pImgs[i];
		m_pImgs[i] = NULL;
	}

	for (POSITION pos = m_Controls.GetHeadPosition(); pos; ) {
		CSkinControl* p = m_Controls.GetNext(pos);
		delete p;
	}
	m_Controls.RemoveAll();
}

bool CSkinWnd::LoadSkin( CWnd* pWnd, LPCTSTR lpszSkinPath, LPCTSTR lpszSection )
{
	m_pWnd = pWnd;
	ASSERT(m_pWnd->GetSafeHwnd());
	//DWORD dwStyle = WS_SYSMENU| WS_CLIPCHILDREN | WS_CLIPSIBLINGS|WS_MINIMIZEBOX|WS_MAXIMIZEBOX|WS_THICKFRAME;
	m_pWnd->ModifyStyle(0, WS_MINIMIZEBOX | WS_MAXIMIZEBOX);
	m_pWnd->ModifyStyleEx(WS_EX_WINDOWEDGE | WS_EX_RIGHTSCROLLBAR, WS_EX_LAYERED);

	ASSERT(lpszSkinPath);
	ASSERT(lpszSection);
	if (!AfxIsValidString(lpszSkinPath)) {
		return false;
	}
	if (!AfxIsValidString(lpszSection)) {
		return false;
	}

	m_strSkinPath = CPathAppend(lpszSkinPath, _T(""));
	LPCTSTR strIniFile = CPathAppend(m_strSkinPath, _T("skin.ini"));
	if (!::PathFileExists(strIniFile))
		return false;

	ASSERT(!m_pSkinConfigFile);
	m_pSkinConfigFile = new CIni(strIniFile);

	// get log image file name
	m_strLogImage = CPathAppend (m_strSkinPath, m_pSkinConfigFile->GetString(lpszSection, _T("logimage")));
	m_crBackground = m_pSkinConfigFile->GetColor(lpszSection, _T("background"));

	// split frame image
	if (!LoadFrameImages(lpszSection)) {
		return false;
	}

	// read panel info
	CStringArray astrPanels;
	m_pSkinConfigFile->GetArray(lpszSection, _T("PANEL"), &astrPanels);
	if (!astrPanels.IsEmpty()){
		CreatePanels (astrPanels);
	}

	// read static 
	CStringArray astrStatic;
	m_pSkinConfigFile->GetArray(lpszSection, _T("STATIC"), &astrStatic);
	if (!astrStatic.IsEmpty()){
		CreateStatics(astrStatic);
	}

	// read indicators info
	CStringArray astrIndicators;
	m_pSkinConfigFile->GetArray(lpszSection, _T("INDICATOR"), &astrIndicators);
	if (!astrIndicators.IsEmpty()){
		CreateIndicators (astrIndicators);
	}

	// read buttons info
	CStringArray astrButton;
	m_pSkinConfigFile->GetArray(lpszSection, _T("BUTTON"), &astrButton);
	if (!astrButton.IsEmpty()) {
		// create buttons
		CreateButtons(astrButton);
	}

	// read progressCtrl info
	CStringArray astrProgress;
	m_pSkinConfigFile->GetArray(lpszSection, _T("PROGRESS"), &astrProgress);
	if (!astrProgress.IsEmpty()){
		CreateProgressCtrls (astrProgress);
	}

	// get MinTrackSize
	m_ptMinTrackSize = m_pSkinConfigFile->GetPoint(lpszSection, _T("MinTrackSize"), &m_ptMinTrackSize);
	RedrawWnd(m_ptMinTrackSize.x, m_ptMinTrackSize.y);

	return true;
}

// load frame image
bool CSkinWnd::LoadFrameImages(LPCTSTR lpszSection )
{
	ASSERT(m_pSkinConfigFile);
	using namespace Gdiplus;

	LPCTSTR lpszImgFile  = CPathAppend (m_strSkinPath, m_pSkinConfigFile->GetString(lpszSection, _T("frameimage")));
	Bitmap imgForm (lpszImgFile);
	if (imgForm.GetPixelFormat() != PixelFormat32bppARGB) {
		TRACE1("Image file %s is invalidate!\n", lpszImgFile);
		return false;
	}

	// split to eight
#define RECT2Rect(name, rc)	{\
		RECT rtTemp = m_pSkinConfigFile->GetRect(lpszSection, _T(#name)); \
		rc = Rect(rtTemp.left, rtTemp.top, rtTemp.right - rtTemp.left, rtTemp.bottom - rtTemp.top); \
	}

	Rect rcTopleft, rcTop, rcTopright, rcLeft, rcRight, rcBottomleft, rcBottom, rcBottomright;
	RECT2Rect(topleft, rcTopleft);
	RECT2Rect(top, rcTop);
	RECT2Rect(topright, rcTopright);
	RECT2Rect(left, rcLeft);
	RECT2Rect(right, rcRight);
	RECT2Rect(bottomleft, rcBottomleft);
	RECT2Rect(bottom, rcBottom);
	RECT2Rect(bottomright, rcBottomright);

	m_pImgTopleft = imgForm.Clone (rcTopleft, PixelFormatDontCare);
	m_pImgTopright = imgForm.Clone (rcTopright, PixelFormatDontCare);
	m_pImgBottomleft = imgForm.Clone (rcBottomleft, PixelFormatDontCare);
	m_pImgBottomright = imgForm.Clone (rcBottomright, PixelFormatDontCare);
#if USE_CACHED_BITMAP
	rcTop.Width = rcBottom.Width = 1;
	rcLeft.Height = rcRight.Height = 1;
#endif
	m_pImgTop = imgForm.Clone (rcTop, PixelFormatDontCare);
	m_pImgLeft = imgForm.Clone (rcLeft, PixelFormatDontCare);
	m_pImgRight = imgForm.Clone (rcRight, PixelFormatDontCare);
	m_pImgBottom = imgForm.Clone (rcBottom, PixelFormatDontCare);

	return true;
}

//=============================================================================
UINT CSkinWnd::CreatePanels(const CStringArray& PanelNames)
{
	ASSERT(m_pSkinConfigFile);

	const INT nButCount = PanelNames.GetCount();
	for (INT i = 0; i < nButCount; i++) {
		LPCTSTR pszSct = PanelNames.GetAt(i);
		if (!m_pSkinConfigFile->IsSectionExist(pszSct))
			continue;

		CString pszCmd = m_pSkinConfigFile->GetString (pszSct, _T("command"), _T("Panel"));
		LPCTSTR pszImg = CPathAppend (m_strSkinPath, m_pSkinConfigFile->GetString (pszSct, _T("image")));
		CSkinControl::AlignFlag	nAlign = (CSkinControl::AlignFlag)m_pSkinConfigFile->GetInt (pszSct, _T("align"));
		CPoint pt = m_pSkinConfigFile->GetPoint(pszSct, _T("offset"));

		// create control
		CSkinPanel* pCtrl = new CSkinPanel;
		if (!pCtrl->CreateCtrl(this, pszCmd, pszImg, pt, nAlign, m_pWnd, s_ControlId++)) {
			delete pCtrl;
			continue;
		}

		// set panel border
		const int border = m_pSkinConfigFile->GetInt (pszSct, _T("border"));
		if (border > -1){
			pCtrl->SetBorder(border);
		}

		m_Controls.AddTail(pCtrl);
	}

	return 0;
}

//=============================================================================
UINT CSkinWnd::CreateStatics(const CStringArray& Names)
{
	ASSERT(m_pSkinConfigFile);

	const INT nButCount = Names.GetCount();
	for (INT i = 0; i < nButCount; i++) {
		LPCTSTR pszSct = Names.GetAt(i);
		if (!m_pSkinConfigFile->IsSectionExist(pszSct))
			continue;

		CString pszCmd = m_pSkinConfigFile->GetString (pszSct, _T("command"), _T("STATIC"));
		LPCTSTR pszImg = CPathAppend (m_strSkinPath, m_pSkinConfigFile->GetString (pszSct, _T("image"), _T("NONE")));
		CSkinControl::AlignFlag	nAlign = (CSkinControl::AlignFlag)m_pSkinConfigFile->GetInt (pszSct, _T("align"));
		CPoint pt = m_pSkinConfigFile->GetPoint(pszSct, _T("offset"));

		// create control
		CSkinStatic* pCtrl = new CSkinStatic;
		if (!pCtrl->CreateCtrl(this, pszCmd, pszImg, pt, nAlign, m_pWnd, s_ControlId++)) {
			delete pCtrl;
			continue;
		}

		// set caption, size, font, color
		CString strCaption = m_pSkinConfigFile->GetString (pszSct, _T("caption"));
		pCtrl->SetWindowText (strCaption);

		CSize size = m_pSkinConfigFile->GetPoint (pszSct, _T("size"));
		if (size.cx && size.cy){
			pCtrl->SetSize(size.cx, size.cy);
		}

		CStringArray astrFont;
		m_pSkinConfigFile->GetArray(pszSct, _T("font"), &astrFont);
		if (astrFont.GetSize() > 2){
			LPCTSTR pszFntName = astrFont.GetAt(0);
			INT nFntSize = _ttoi (astrFont.GetAt(1));
			INT nFntStyle = _ttoi (astrFont.GetAt(2));
			pCtrl->SetFont(pszFntName, nFntSize, nFntStyle);
		}

		COLORREF clFnt = m_pSkinConfigFile->GetColor(pszSct, _T("color"));
		pCtrl->SetFontColor(clFnt);

		m_Controls.AddTail(pCtrl);
	}

	return 0;
}

//=============================================================================
UINT CSkinWnd::CreateIndicators(const CStringArray& Names)
{
	ASSERT(m_pSkinConfigFile);

	const INT nButCount = Names.GetCount();
	for (INT i = 0; i < nButCount; i++) {
		LPCTSTR pszSct = Names.GetAt(i);
		if (!m_pSkinConfigFile->IsSectionExist(pszSct))
			continue;

		CString pszCmd = m_pSkinConfigFile->GetString (pszSct, _T("command"), _T("LCD"));
		LPCTSTR pszImg = CPathAppend (m_strSkinPath, m_pSkinConfigFile->GetString (pszSct, _T("image"), _T("NONE")));
		CSkinControl::AlignFlag	nAlign = (CSkinControl::AlignFlag)m_pSkinConfigFile->GetInt (pszSct, _T("align"));
		CPoint pt = m_pSkinConfigFile->GetPoint(pszSct, _T("offset"));

		// create control
		CSkinLCD* pCtrl = new CSkinLCD;
		if (!pCtrl->CreateCtrl(this, pszCmd, pszImg, pt, nAlign, m_pWnd, s_ControlId++)) {
			delete pCtrl;
			continue;
		}

		// set size, font, color
		CSize size = m_pSkinConfigFile->GetPoint (pszSct, _T("size"));
		if (size.cx && size.cy){
			pCtrl->SetSize(size.cx, size.cy);
		}

		CStringArray astrFont;
		m_pSkinConfigFile->GetArray(pszSct, _T("font"), &astrFont);
		if (astrFont.GetSize() > 2){
			LPCTSTR pszFntName = astrFont.GetAt(0);
			INT nFntSize = _ttoi (astrFont.GetAt(1));
			INT nFntStyle = _ttoi (astrFont.GetAt(2));
			pCtrl->SetFont(pszFntName, nFntSize, nFntStyle);
		}

		COLORREF clFnt = m_pSkinConfigFile->GetColor(pszSct, _T("color"));
		pCtrl->SetFontColor(clFnt);

		m_Controls.AddTail(pCtrl);
	}

	return 0;
}

//=============================================================================
UINT CSkinWnd::CreateButtons( const CStringArray& ButtonNames )
{
	ASSERT(m_pSkinConfigFile);

	const INT nButCount = ButtonNames.GetCount();
	for (INT i = 0; i < nButCount; i++) {
		LPCTSTR pszSct = ButtonNames.GetAt(i);
		if (!m_pSkinConfigFile->IsSectionExist(pszSct))
			continue;

		CString pszCmd = m_pSkinConfigFile->GetString (pszSct, _T("command"));
		LPCTSTR pszImg = CPathAppend (m_strSkinPath, m_pSkinConfigFile->GetString (pszSct, _T("image")));
		CSkinControl::AlignFlag	nAlign = (CSkinControl::AlignFlag)m_pSkinConfigFile->GetInt (pszSct, _T("align"));
		CPoint pt = m_pSkinConfigFile->GetPoint(pszSct, _T("offset"));

		// create button
		CSkinButton* pButton = new CSkinButton;
		if (!pButton->CreateCtrl(this, pszCmd, pszImg, pt, nAlign, m_pWnd, s_ControlId++)) {
			delete pButton;
			continue;
		}

		// tooltip
		CString strHint = m_pSkinConfigFile->GetString (pszSct, _T("hint"));
		if (!strHint.IsEmpty())
			pButton->SetTooltip(strHint);

		m_Controls.AddTail(pButton);
	}

	return 0;
}

//=============================================================================
UINT CSkinWnd::CreateProgressCtrls( const CStringArray& ProgressCtrlNames )
{
	ASSERT(m_pSkinConfigFile);

	const INT nButCount = ProgressCtrlNames.GetCount();
	for (INT i = 0; i < nButCount; i++) {
		LPCTSTR pszSct = ProgressCtrlNames.GetAt(i);
		if (!m_pSkinConfigFile->IsSectionExist(pszSct))
			continue;

		CString pszCmd = m_pSkinConfigFile->GetString (pszSct, _T("command"));
		LPCTSTR pszImg = CPathAppend (m_strSkinPath, m_pSkinConfigFile->GetString (pszSct, _T("image")));
		CSkinControl::AlignFlag	nAlign = (CSkinControl::AlignFlag)m_pSkinConfigFile->GetInt (pszSct, _T("align"));
		CPoint pt = m_pSkinConfigFile->GetPoint(pszSct, _T("offset"));

		// create control
		CSkinProgressCtrl* pCtrl = new CSkinProgressCtrl;
		if (!pCtrl->CreateCtrl(this, pszCmd, pszImg, pt, nAlign, m_pWnd, s_ControlId++)) {
			delete pCtrl;
			continue;
		}

		// tooltip
		CString strHint = m_pSkinConfigFile->GetString (pszSct, _T("hint"));
		if (!strHint.IsEmpty())
			pCtrl->SetTooltip(strHint);

		m_Controls.AddTail(pCtrl);
	}

	return 0;
}

//=============================================================================
void CSkinWnd::OnSize( HWND /*hwnd*/, UINT state, int cx, int cy )
{
	UpdateWnd(0);
}

void CSkinWnd::RedrawWnd(const int cx, const int cy, const UINT nCtrlID)
{
	if (m_pCacheFrameImage != NULL){
		const int cxCur = m_pCacheFrameImage->GetWidth();
		const int cyCur = m_pCacheFrameImage->GetHeight();
		if (cxCur != cx || cyCur != cy){
			delete m_pCacheFrameImage;
			m_pCacheFrameImage = NULL;
		}
	}

	if (NULL == m_pCacheFrameImage){
		Gdiplus::Bitmap bmpFrame(cx, cy, PixelFormat32bppARGB);
		m_pCacheFrameImage = bmpFrame.Clone(0, 0, cx, cy, PixelFormatDontCare);

		// draw new frame image
		if (!DrawFrame(m_pCacheFrameImage, cx, cy))
			return;
	}

	CAutoPtr<Gdiplus::Bitmap> pBmp (m_pCacheFrameImage->Clone(0, 0, cx, cy, PixelFormatDontCare));

	DrawControls (pBmp, nCtrlID);
	//
	UpdateLayered(pBmp);
}

bool CSkinWnd::DrawFrame( Gdiplus::Bitmap* pBmp, const int cxForm, const int cyForm )
{
	using namespace Gdiplus;
	if (NULL == m_pImgTopleft)
		return false;

	const INT x1 = m_pImgTopleft->GetWidth(), y1 = m_pImgTopleft->GetHeight();

	// draw window frame
	Graphics graphic (pBmp);

	// top
	graphic.DrawImage(m_pImgTopleft, 0, 0, m_pImgTopleft->GetWidth(), m_pImgTopleft->GetHeight());
	const INT cxTop = cxForm - x1 - m_pImgTopright->GetWidth();
#if USE_CACHED_BITMAP
	CachedBitmap  chbmTop(m_pImgTop, &graphic);
	for (INT xPos = 0; xPos < cxTop; xPos++) {
		graphic.DrawCachedBitmap (&chbmTop, xPos + x1, 0);
	}
#else
	TextureBrush brTop (m_pImgTop);
	graphic.FillRectangle(&brTop, x1, 0, cxTop, y1);
#endif
	graphic.DrawImage(m_pImgTopright, cxTop + x1, 0, m_pImgTopright->GetWidth(), m_pImgTopright->GetHeight());

	// middle
	const INT cyLeft = cyForm - y1 - m_pImgBottomleft->GetHeight();
#if USE_CACHED_BITMAP
	CachedBitmap  chbmLeft(m_pImgLeft, &graphic);
	CachedBitmap  chbmRight(m_pImgRight, &graphic);
	for (INT yPos = 0; yPos < cyLeft; yPos++) {
		graphic.DrawCachedBitmap(&chbmLeft, 0, yPos + y1);
		graphic.DrawCachedBitmap(&chbmRight, cxForm - m_pImgRight->GetWidth(), yPos + y1);
	}
#else
	TextureBrush brLeft (m_pImgLeft);
	TextureBrush brRight (m_pImgRight);

	graphic.FillRectangle (&brLeft, 0, y1, m_pImgLeft->GetWidth(), cyLeft);
	graphic.FillRectangle (&brRight, cxTop + x1, y1, m_pImgRight->GetWidth(), cyLeft);
#endif

	// bottom
	graphic.DrawImage(m_pImgBottomleft, 0, cyLeft + y1, m_pImgBottomleft->GetWidth(), m_pImgBottomleft->GetHeight());
#if USE_CACHED_BITMAP
	CachedBitmap  chbmBottom(m_pImgBottom, &graphic);
	for (INT xPos = 0; xPos < cxTop; xPos++) {
		graphic.DrawCachedBitmap(&chbmBottom, xPos + x1, cyLeft + y1);
	}
#else
	TextureBrush brBottom (m_pImgBottom);
	graphic.FillRectangle(&brBottom, x1, cyLeft + y1, cxTop, m_pImgBottom->GetHeight());
#endif
	graphic.DrawImage(m_pImgBottomright, cxTop + x1, cyLeft + y1, m_pImgBottomright->GetWidth(), m_pImgBottomright->GetHeight());

	// calculate client rectangle
	m_rectClient.SetRect(m_pImgLeft->GetWidth(), m_pImgTopleft->GetHeight(),
						 cxForm - m_pImgRight->GetWidth(), cyForm - m_pImgBottomright->GetHeight());

	return true;
}

void CSkinWnd::DrawControls(Gdiplus::Bitmap* pBmp, UINT nCtrlID)
{
	//  [6/5/2008 Dean] draw all controls
	for (POSITION pos = m_Controls.GetHeadPosition(); pos; ) {
		CSkinControl* pCtrl = m_Controls.GetNext(pos);
		if (NULL == pCtrl)
			continue;
		pCtrl->DrawControl (pBmp);
	}

}

void CSkinWnd::OnLButtonDown( HWND hwnd, BOOL fDoubleClick, int x, int y, UINT keyFlags )
{
	::PostMessage(hwnd, WM_NCLBUTTONDOWN, HTCAPTION, MAKELPARAM(x, y));
}

//=============================================================================
void CSkinWnd::OnGetMinMaxInfo( HWND hwnd, LPMINMAXINFO lpMinMaxInfo )
{
	if (::IsWindow(hwnd)) {
		lpMinMaxInfo->ptMinTrackSize = m_ptMinTrackSize;
	}
}

//=============================================================================
void CSkinWnd::UpdateWnd( UINT nID )
{
	CRect rt;
	m_pWnd->GetWindowRect(rt);
	RedrawWnd (rt.Width(), rt.Height(), nID);

}

//=============================================================================
LONG CSkinWnd::OnEvent( LPCTSTR lpszCmd, const CComVariant& vEvent )
{
	CCommandSink cmdSink (m_pWnd);
	return cmdSink.HandleEvent(lpszCmd, vEvent);
}

